<script lang="ts">
  import {
    download_file,
    FILE_OWNER_TYPE_USER_PHOTO,
    save_tmp_file_base64,
    set_file_owner,
    write_thumb_image_file,
    type FsProgressEvent,
  } from "$lib/api/fs";
  import { update_basic } from "$lib/api/user";
  import { userStore } from "$lib/stores/user.svelte";
  import { request } from "$lib/utils/request";
  import { Modal } from "@skeletonlabs/skeleton-svelte";
  import type { Store as ToastStore } from "@zag-js/toast";
  import { Channel } from "@tauri-apps/api/core";
  import { readFile, remove as removeFile } from "@tauri-apps/plugin-fs";
  import { FolderOpenDot, X } from "lucide-svelte";
  import { getContext } from "svelte";
  import { Cropper, type CropperInstance } from "svelte-cropper";
  import { open as dialog_open } from "@tauri-apps/plugin-dialog";

  const toast: ToastStore = getContext("toast");

  interface ModalProps {
    onClose: () => void;
  }

  let props: ModalProps = $props();

  let cropper: CropperInstance | null = $state(null);
  let imageRef: HTMLImageElement | null = $state(null);

  let imageSrc = $state(null as string | null);

  function convertToB64(data: Uint8Array): string {
    const chunk = 8 * 1024;
    const dataStrList = [] as string[];
    for (let i = 0; i < data.length / chunk; i++) {
      const buf = data.slice(i * chunk, (i + 1) * chunk);
      dataStrList.push(String.fromCharCode(...buf));
    }
    return btoa(dataStrList.join(""));
  }

  async function adjustImageSrc(srcValue: string) {
    if (srcValue.startsWith("fs:")) {
      const parts = srcValue.substring(3).split("/");
      if (parts.length != 2) {
        return;
      }
      const fsId = parts[0];
      const fileId = parts[1];
      const onEvent = new Channel<FsProgressEvent>();
      const res = await download_file(
        userStore.userSession,
        fsId,
        fileId,
        "",
        onEvent
      );

      if (res.exist_in_local) {
        const data = await readFile(res.local_path);
        imageSrc = `data:image/*;base64,${convertToB64(data)}`;
      }
    } else if (srcValue != "") {
      imageSrc = srcValue;
    } else if (srcValue == "") {
      imageSrc = "/favicon.png";
    }
  }

  adjustImageSrc(userStore.userInfo?.basic_info.logo_uri ?? "");

  async function selectLocalImage() {
    const filePath = await dialog_open({
      title: "选择图片",
      filters: [
        {
          name: "图片",
          extensions: ["bmp", "png", "jpg", "jepg"],
        },
      ],
    });
    if (filePath == null) {
      return;
    }
    const data = await readFile(filePath);
    imageSrc = `data:image/*;base64,${convertToB64(data)}`;
  }

  async function saveCropData() {
    //截图图片
    if (cropper == null || imageRef == null) {
      return;
    }
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");
    if (ctx == null) {
      return;
    }
    const cropSize = cropper.getData();
    canvas.width = cropSize.width;
    canvas.height = cropSize.height;

    ctx.drawImage(
      imageRef,
      cropSize.x,
      cropSize.y,
      cropSize.width,
      cropSize.height,
      0,
      0,
      cropSize.width,
      cropSize.height
    );

    const imgUrlData = canvas.toDataURL("image/png");
    if (!imgUrlData.includes("base64,")) {
      return;
    }
    const pos = imgUrlData.indexOf(",");
    const rawData = imgUrlData.substring(pos + 1);
    //上传文件
    const filePath = await save_tmp_file_base64("crop.png", rawData);
    const onEvent = new Channel<FsProgressEvent>();
    const uploadRes = await request(
      write_thumb_image_file(
        userStore.userSession,
        userStore.userInfo?.user_id ?? "",
        filePath,
        64,
        64,
        onEvent
      )
    );
    await removeFile(filePath); //移除临时文件
    await request(
      set_file_owner({
        session_id: userStore.userSession,
        fs_id: userStore.userInfo?.user_id ?? "",
        file_id: uploadRes.file_id,
        owner_type: FILE_OWNER_TYPE_USER_PHOTO,
        owner_id: userStore.userInfo?.user_id ?? "",
      })
    );
    //更新logo
    const newLogoUri = `fs:${userStore.userInfo?.user_id ?? ""}/${uploadRes.file_id}`;
    await request(
      update_basic({
        session_id: userStore.userSession,
        basic_info: {
          display_name: userStore.userInfo?.basic_info.display_name ?? "",
          logo_uri: newLogoUri,
        },
      })
    );
    if (userStore.userInfo != null) {
      userStore.userInfo.basic_info.logo_uri = newLogoUri;
    }
    toast.create({
      title: "成功",
      description: "保存头像成功!",
      type: "success",
    });
    props.onClose();
  }

  $effect(() => {
    return () => {
      if (imageSrc != null && cropper != null) {
        cropper.destroy();
        cropper = null;
      }
    };
  });
</script>

<Modal
  open={true}
  triggerBase="btn preset-tonal"
  contentBase="card bg-surface-200-800 p-4 space-y-4 shadow-xl w-[500px]"
  backdropClasses="backdrop-blur-xs"
  backdropBase="backdrop-none"
  onOpenChange={(ev) => {
    if (!ev.open) {
      props.onClose();
    }
  }}
>
  {#snippet content()}
    <header class="flex justify-between">
      <h4 class="h4">修改头像</h4>
      <button
        type="button"
        class="btn preset-ghost-primary-500"
        onclick={() => props.onClose()}
      >
        <X />
      </button>
    </header>
    {#if imageSrc != null}
      <Cropper
        class=" h-[300px]"
        bind:cropper
        bind:image_ref={imageRef}
        src={imageSrc}
        cropper_props={{
          viewMode: 2,
          dragMode: "crop",
          initialAspectRatio: 1,
          aspectRatio: 1,
        }}
      />
    {/if}
    <footer class="flex justify-between">
      <div class="flex-1">
        <button
          type="button"
          class="btn preset-tonal"
          onclick={() => selectLocalImage()}
          ><FolderOpenDot size={20} />选择本地文件</button
        >
      </div>
      <div class="flex gap-4">
        <button
          type="button"
          class="btn preset-tonal"
          onclick={() => props.onClose()}>取消</button
        >
        <button
          type="button"
          class="btn preset-filled-primary-900-100"
          disabled={imageSrc == null}
          onclick={() => saveCropData()}>保存</button
        >
      </div>
    </footer>
  {/snippet}
</Modal>
